// ****************************************************************************
// HushDB
//
// AUTHOR:  Justin Shen
//
// NOTES: 
//     Defines Page type and related consts
//
// ****************************************************************************

#ifndef HUSHDB_PAGE_H
#define HUSHDB_PAGE_H

#include <memory.h>

#include "..\Common\BasicType.h"

namespace Hush
{
    namespace Database
    {    
        typedef Int32 PageId;
        typedef Int32 SlotId;
        const UInt32 PageSize = 8192;
        const PageId InvalidPageId = -1;
        const PageId EndOfPage = -2;
        const SlotId InvalidSlotId = 1; // slotId should be 0 or negative. 1 indicates invalid slot id.
        const SlotId EndOfSlot = -2;        

        struct RowId
        {
            RowId(PageId pageId = InvalidPageId, SlotId slotId = InvalidSlotId) 
                : pageId(pageId), slotId(slotId)
            {}
            PageId pageId;
            SlotId slotId;
        };

        struct RowPtr
        {
            static const Int32 EndOfRow = -1;
            RowPtr(Byte* data = 0, Int32 length = 0)
                : data(data), length(length)
            {
            }
            Byte* data;
            Int32 length;
        };
        
        template <typename T>
        static inline RowPtr MakeRowPtr(T* data)
        {
            return RowPtr((Byte*)data, sizeof(T));
        }
        
        struct SlotInfo
        {
            static const Int16 EmptySlotOffset = -1;
            static const SlotInfo EmptySlot();

            SlotInfo()
                : offset(EmptySlotOffset), length(-1)
            {
            }

            Int16 offset; // offset within a page
            Int16 length; // length of a row
        };


        // Layout requirements:
        //  - The data region starts from the beginning.
        //  - Fields starts from the end.
        //  - The first field should be PageId;
        class PageData
        {
        public:            
            static const UInt16 PageFixedDataSize = sizeof(PageId);
            static const UInt16 DataRegionSize = PageSize-PageFixedDataSize;

            PageData()
                : currentPage(InvalidPageId)
            {
                memset(this->data, 0, PageData::DataRegionSize);
            }

        protected:
            Byte data[DataRegionSize];
            PageId currentPage;
        };

        // The layout of DataPage's data should be compatible with PageData so that
        // a DataPage can be casted to a Page.
        class DataPageData
        {
        public:
            static const UInt16 PageFixedDataSize = sizeof(SlotInfo) + sizeof(Int32) + 2*sizeof(PageId) + 2*sizeof(Int16) + sizeof(SlotId);
            static const UInt16 DataRegionSize = PageSize-PageFixedDataSize;

            DataPageData()
                : currentPage(InvalidPageId), slotCount(0), freeSpaceOffset(0), maxSlotId(InvalidSlotId), nextPage(EndOfPage)
            {
                memset(this->data, 0, DataPageData::DataRegionSize);
            }

        protected:
            // HeapPage layout
            Byte data[DataRegionSize]; // data region
            SlotInfo slots[1]; // grows backwards to data
            Int32 slotCount;            
            Int16 freeSpaceOffset; // the starting offset of free space.
            Int16 padding; // this is for memory alignment
            SlotId maxSlotId;
            PageId nextPage;

            // This is from Page, should be at the end.
            PageId currentPage;
        };

        template <typename TData>
        class PageImpl : public TData
        {
        public:
            PageImpl(PageId pageId = InvalidPageId)                
            {
                currentPage = pageId;
            }

            PageId GetPageId() const { return currentPage; }
            void SetPageId(PageId pageId) { currentPage = pageId; }
        };

        typedef PageImpl<PageData> Page;        
                
        template <typename TData>
        class DataPageImpl : public PageImpl<TData>
        {        
        public:
            DataPageImpl(PageId pageId = InvalidPageId)
            {
                currentPage = pageId;                                   
            }

            Int32 GetRowCount() const { return slotCount; }
            Int16 GetFreeSpaceOffset() const { return freeSpaceOffset; }

            // When inserting new row, the needed space is length of data + length of a SlotInfo
            UInt16 GetAvailableSpace() const { return DataRegionSize - freeSpaceOffset - ((-this->maxSlotId)+1)*sizeof(SlotInfo); }
            
            RowId AllocateRow(Int32 length)
            {
                SlotId slotId = InvalidSlotId; 
                // Try to find an empty slot id
                for (int i = 0; i >= this->maxSlotId; i--)
                {
                    if (slots[i].offset == SlotInfo::EmptySlotOffset)
                    {
                        slotId = i;
                        break;
                    }
                }

                if (slotId == InvalidSlotId)
                {
                    slotId = this->maxSlotId-1;
                    this->maxSlotId--;
                }

                this->slotCount++;        

                this->slots[slotId].length = length;        
                this->slots[slotId].offset = freeSpaceOffset;                

                this->freeSpaceOffset += length;

                if (slotId < this->maxSlotId)
                {
                    this->maxSlotId = slotId;
                }

                return RowId(this->currentPage, slotId);

            }

            RowId InsertRow(const RowPtr& rowPtr)
            {
				RowId rowId = AllocateRow(rowPtr.length);
                memcpy((void*)&this->data[slots[rowId.slotId].offset], rowPtr.data, rowPtr.length);

                return rowId;
            }

            RowPtr GetRowPtr(const SlotId& slotId)
            {            
                return RowPtr(&data[slots[slotId].offset], slots[slotId].length);
            }

            RowPtr GetRowPtr(const RowId& rowId)
            {
                return GetRowPtr(rowId.slotId);
            }

            Int16 GetRowOffset(const SlotId& slotId) const
            {
                return slots[slotId].offset;
            }
            
            Int16 GetRowOffset(const RowId& rowId) const
            {
                return slots[rowId.slotId].offset;
            }

            void DeleteRow(const SlotId& slotId)
            {
                Int16 startOffset = slots[slotId].offset;
                Int16 length = slots[slotId].length;
                Int16 endOffset = startOffset + length;
            

                //We need to move the data after endOffset to startOffset if there is any
                if (endOffset < this->freeSpaceOffset)
                {
                    memcpy(&data[startOffset], &data[endOffset], this->freeSpaceOffset-endOffset);
                }

                slots[slotId].offset = SlotInfo::EmptySlotOffset;

                for (int i = slotId-1; i >= this->maxSlotId; i--)
                {
                    if (slots[i].offset != SlotInfo::EmptySlotOffset)
                    {
                        slots[i].offset -= length;
                    }
                }

                if (slotId == this->maxSlotId)
                {
                    this->maxSlotId++;
                }

                this->freeSpaceOffset -= length;            
                this->slotCount--;
            }

            void DeleteRow(RowId rowId)
            {
                this->DeleteRow(rowId.slotId);
            }

            SlotId GetFirstSlotId()
            {
                return GetNextValidSlotId(InvalidSlotId);
            }

            RowId GetFirstRow()
            {
                return RowId(this->currentPage, this->GetFirstSlotId());
            }

            RowId GetNextRow(RowId rowId)
            {
                return RowId(currentPage, GetNextValidSlotId(rowId.slotId));
            }

            SlotId GetNextValidSlotId(SlotId current)
            {
                SlotId id = current-1;
                for (; id >= maxSlotId; id--)
                {
                    if (slots[id].offset != SlotInfo::EmptySlotOffset)
                    {
                        return id;
                    }
                }

                if (id < maxSlotId)
                {
                    id = EndOfSlot;
                }

                return id;
            }

            void SetNextPageId(PageId nextPage) { this->nextPage = nextPage; }
            PageId GetNextPageId() { return this->nextPage; }
        };

        // Stores rows on the page. One row for a slot.
        typedef DataPageImpl<DataPageData> DataPage;

    }
    
}

#endif